using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

using Yorrick.FlickrMetadataSynchr.Data;

namespace Yorrick.FlickrMetadataSynchr.Match
{
   /// <summary>
   /// Base class for classes that can match pictures based on their metadata.
   /// </summary>
   internal abstract class MetadataMatcherBase : IMetadataMatcher
   {
      #region Private fields
      /// <summary>
      /// A list of matched picture pairs.
      /// </summary>
      private List<PicturePair> _matchedPictures;

      /// <summary>
      /// A list of pictures on Flickr that should be matched.
      /// </summary>
      private IList<FlickrPictureMetadata> _flickrPicturesToMatch;

      /// <summary>
      /// A list of local pictures that should be matched.
      /// </summary>
      private IList<LocalPictureMetadata> _localPicturesToMatch;

      /// <summary>
      /// A list of pictures that exist on Flickr but are unmatched with a local picture.
      /// </summary>
      private List<FlickrPictureMetadata> _unmatchedFlickrPictures;

      /// <summary>
      /// A list of local pictures that are unmatched with a Flickr picture.
      /// </summary>
      private List<LocalPictureMetadata> _unmatchedLocalPictures;

      /// <summary>
      /// Whether or not matching is done.
      /// </summary>
      private bool _isMatchDone;
      #endregion

      #region Constructor
      /// <summary>
      /// Constructs an instance.
      /// </summary>
      protected MetadataMatcherBase()
      {
      }
      #endregion

      #region Protected properties
      /// <summary>
      /// Gets the list of matched picture pairs.
      /// </summary>
      protected List<PicturePair> MatchedPictures
      {
         get
         {
            return _matchedPictures;
         }
      }

      /// <summary>
      /// Gets the list of pictures on Flickr that should be matched.
      /// </summary>
      protected IList<FlickrPictureMetadata> FlickrPicturesToMatch
      {
         get
         {
            return _flickrPicturesToMatch;
         }
      }


      /// <summary>
      /// Gets the list of local pictures that should be matched.
      /// </summary>
      protected IList<LocalPictureMetadata> LocalPicturesToMatch
      {
         get
         {
            return _localPicturesToMatch;
         }
      }

      /// <summary>
      /// Gets the list of pictures that exist on Flickr but are unmatched with a local picture.
      /// </summary>
      protected List<FlickrPictureMetadata> UnmatchedFlickrPictures
      {
         get
         {
            return _unmatchedFlickrPictures;
         }
      }

      /// <summary>
      /// Gets the list of local pictures that are unmatched with a Flickr picture.
      /// </summary>
      protected List<LocalPictureMetadata> UnmatchedLocalPictures
      {
         get
         {
            return _unmatchedLocalPictures;
         }
      }


      #endregion

      #region IMetadataMatcher implementation
      /// <summary>
      /// Sets the picture lists that should be used for matching.
      /// </summary>
      /// <param name="localList">A list of local pictures.</param>
      /// <param name="flickrList">A list of Flickr pictures.</param>
      /// <remarks>This method should be called before any other method is called.</remarks>
      public void SetPictureLists(IList<LocalPictureMetadata> localList, IList<FlickrPictureMetadata> flickrList)
      {
         if (localList == null)
         {
            throw new ArgumentNullException("localList");
         }

         if (flickrList == null)
         {
            throw new ArgumentNullException("flickrList");
         }

         _localPicturesToMatch = localList;
         _flickrPicturesToMatch = flickrList;

         _unmatchedLocalPictures = new List<LocalPictureMetadata>(localList.Count);
         _unmatchedFlickrPictures = new List<FlickrPictureMetadata>(flickrList.Count);
         _matchedPictures = new List<PicturePair>( Math.Min(flickrList.Count, localList.Count) );
      }
         
      /// <summary>
      /// Matches the pictures in the local set and the Flickr set based on datetime taken and
      /// returns a list of matched pictures.
      /// </summary>
      /// <returns>The list of matched pictures.</returns>
      public IList<PicturePair> GetMatchedPictures()
      {
         EnsureMatchDone();

         return _matchedPictures.AsReadOnly();
      }

      /// <summary>
      /// Returns a list of Flickr pictures that could not be matched with a local picture.
      /// </summary>
      /// <returns>The list of unmatched Flickr pictures.</returns>
      public IList<FlickrPictureMetadata> GetUnmatchedFlickrPictures()
      {
         EnsureMatchDone();

         return _unmatchedFlickrPictures.AsReadOnly();
      }

      /// <summary>
      /// Returns a list of local pictures that could not be matched with a Flickr picture.
      /// </summary>
      /// <returns>The list of unmatched local pictures.</returns>
      public IList<LocalPictureMetadata> GetUnmatchedLocalPictures()
      {
         EnsureMatchDone();

         return _unmatchedLocalPictures.AsReadOnly();
      }
      #endregion

      #region Protected methods
      /// <summary>
      /// Performs the matching of Flickr pictures with local pictures.
      /// </summary>
      /// <remarks>This method will only be called once per instance.</remarks>
      protected abstract void DoMatch();
      #endregion

      #region Private methods
      /// <summary>
      /// Makes sure that matching is done (at most once).
      /// </summary>
      private void EnsureMatchDone()
      {
         if (!_isMatchDone)
         {
            EnsureListsSet();

            DoMatchInternal();
         }
      }

      /// <summary>
      /// Ensures that the lists of local and Flickr pictures to operate on have been set.
      /// </summary>
      /// <exception cref="InvalidOperationException">Thrown if one or more lists
      /// have not been set.</exception>
      private void EnsureListsSet()
      {
         if (_localPicturesToMatch == null)
         {
            throw new InvalidOperationException("The list of local pictures to match must be set.");
         }

         if (_flickrPicturesToMatch == null)
         {
            throw new InvalidOperationException("The list of Flickr pictures to match must be set.");
         }
      }

      /// <summary>
      /// Performs the matching of Flickr pictures with local pictures.
      /// </summary>
      /// <remarks>This method should only be called once.</remarks>
      private void DoMatchInternal()
      {
         if (_isMatchDone)
         {
            throw new InvalidOperationException("Match is already done and can't be done more than once.");
         }

         DoMatch();

         _isMatchDone = true;
      }
      #endregion
   }
}
